package com.zhbcm.outupload.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.zhbcm.outupload.common.constant.UpLoadConstant;
import com.zhbcm.outupload.common.response.ApiResult;
import com.zhbcm.outupload.common.response.ResultUtil;
import com.zhbcm.outupload.config.UploadProperties;
import com.zhbcm.outupload.entity.MultipartFileParam;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.activation.MimetypesFileTypeMap;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author 朝花不迟暮
 * @version 1.0
 * @date 2021/6/28 23:22
 */
@Service
public class UploadFileUtil {
    private static final Logger log = LoggerFactory.getLogger(UploadFileUtil.class);
    private String FORMAT = null;
    private final Integer cacheTime = 60 * 60 * 24;

    @Resource
    private UploadProperties uploadProperties;
    @Resource
    private RedisUtil redisUtil;

    /**
     * 上传
     *
     * @param file 网络文件传输
     * @return 文件的不带网络前缀的地址
     */
    public String upload(MultipartFile file) {
        FORMAT = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        File folder = new File(uploadProperties.getFileLocation() + FORMAT);
        if (!folder.isDirectory() && !folder.mkdirs()) {
            return "文件夹创建失败";
        }
        String oldName = file.getOriginalFilename();
        assert oldName != null;
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            file.transferTo(new File(folder, newName));
            //以上都是普通代码, 这里的/files/ 对应的就是你在WebMvcConfig设置的地址映射
            return FORMAT + "/" + newName;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上传失败！";
    }

    /**
     * 上传
     *
     * @param file 本地文件传输
     * @return 文件的不带网络前缀的地址
     */
    public String upload(File file) {
        FORMAT = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        File folder = new File(uploadProperties.getFileLocation() + FORMAT);
        if (!folder.isDirectory() && !folder.mkdirs()) {
            return "文件夹创建失败";
        }
        String oldName = file.getName();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        FileUtil.copy(file, new File(folder, newName), false);
        //以上都是普通代码, 这里的/files/ 对应的就是你在WebMvcConfig设置的地址映射
        return FORMAT + "/" + newName;
    }

    public String fileServerUrl() {
        HttpServletRequest req = ServerUtil.getRequest();
        return req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort()
                + uploadProperties.getResourceHandler();
    }

    /**
     * 下载
     *
     * @param url      网络地址 http://localhost:8080/files/2021-06-29/5c8ec19a-7cfa-46d2-9310-a9197b3fb0b7.jpg
     * @param response
     */
    public void downloadImg(String url, HttpServletResponse response) {
        try {
            URL url1 = new URL(url);
            URLConnection urlConnection = url1.openConnection();
            String ext = url.substring(url.lastIndexOf("."));
            String contentType = getContentType(url);
            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment;filename=" + IdUtil.simpleUUID() + ext);
            InputStream is = urlConnection.getInputStream();
            OutputStream os = response.getOutputStream();
            IOUtils.copy(is, os);
            os.flush();
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方式二
     * 该方式支持本地文件，也支持http/https远程文件
     *
     * @param fileUrl
     */
    private static String getContentType(String fileUrl) {
        String contentType = null;
        try {
            contentType = new MimetypesFileTypeMap().getContentType(new File(fileUrl));
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("getContentType, File ContentType is : {} " + contentType);
        return contentType;
    }

    /**
     * 分片上传与断点续传
     *
     * @param multipartFileParam 分片实体
     * @return 待定
     */
    public ApiResult uploadAppendFile(MultipartFileParam multipartFileParam) {
        Map<String, String> map = new HashMap<>();
        //当前分片的位置
        long chunk = multipartFileParam.getChunkNumber();
        //分片总数
        long totalChunks = multipartFileParam.getTotalChunks();
        //文件大小（并非指总文件大小，而是指当前已经传了多大，这里我是写成文件位置的偏移量）
        long fileSize = multipartFileParam.getFileSize();
        // 任务ID 随机数生成
        String taskId = multipartFileParam.getTaskId();
        // 分片本体
        MultipartFile file = multipartFileParam.getFile();
        // 文件名
        String fileName = multipartFileParam.getFileName();
        // 文件后缀
        String extName = FileUtil.extName(fileName);
        // 路径分隔符
        String separator = FileUtil.FILE_SEPARATOR;

        FORMAT = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        File folder = new File(uploadProperties.getFileLocation() + FORMAT);
        if (!folder.isDirectory() && !folder.mkdirs()) {
            return ResultUtil.errMsg("文件夹创建失败");
        }

        // 存放路径
        String localPath = folder.getPath() + separator;
        File tempFile = null;
        RandomAccessFile raf = null;
        InputStream is = null;
        try {
            if (chunk == 1) {
                //生成临时文件名称，以tmp为后缀
                String tempFileName = taskId + fileName.substring(fileName.lastIndexOf(".")) + "_tmp";
                File fileDir = new File(localPath);
                if (!fileDir.exists()) {
                    fileDir.mkdirs();
                }
                //生成临时文件，后续所有的分片文件都往这里存入
                tempFile = new File(localPath, tempFileName);
                if (!tempFile.exists()) {
                    tempFile.createNewFile();

                }
                raf = new RandomAccessFile(tempFile, "rw");
                is = file.getInputStream();
                // 因为是第一片，所以他的起始位置是0
                raf.seek(0);
                int len = 0;
                byte[] bytes = new byte[1024 * 10];
                while ((len = is.read(bytes)) != -1) {
                    raf.write(bytes, 0, len);
                }
                raf.close();
                is.close();
                //往redis中记录当前分片数和文件路径
                redisUtil.setObject(UpLoadConstant.chunkNum + taskId, chunk, cacheTime);
                redisUtil.setObject(UpLoadConstant.fastDfsPath + taskId, tempFile.getPath(), cacheTime);
                map.put("result", "上传成功");
            } else {
                //进到这里说明他已经不是第一片了，说明他可能是续传
                String path = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + taskId);
                is = file.getInputStream();
                raf = new RandomAccessFile(path, "rw");
                raf.seek(fileSize);
                int len = 0;
                byte[] bytes = new byte[1024 * 10];
                while ((len = is.read(bytes)) != -1) {
                    raf.write(bytes, 0, len);
                }
                redisUtil.setObject(UpLoadConstant.chunkNum + taskId, chunk, cacheTime);
                raf.close();
                is.close();
            }
            String md5 = (String) redisUtil.getObject(UpLoadConstant.task + taskId);
            HashMap<String, String> redisMap = new HashMap<>();
            /*这里是关键，我的分片上传可能与大家平时看到的分片上传不太一样，有些人的分片偏移量是自己后台计算出来的，我们这里是
             * 通过后台返给前端，前端拿到之后再去加上下一片分片计算出新的起始位置再发送到后端*/
            redisMap.put("fileSize", fileSize + "");
            redisMap.put("taskId", taskId);
            redisUtil.setHashAsMap(UpLoadConstant.fileMd5 + md5, redisMap, cacheTime);
            if (chunk == totalChunks) {
                String path = (String) redisUtil.getObject(UpLoadConstant.fastDfsPath + taskId);
                FileUtil.rename(new File(path), taskId + "." + extName, true);
                map.put("result", "上传完毕");
                map.put("url", taskId + "." + extName);
                redisUtil.del(UpLoadConstant.fileMd5 + md5);
                redisUtil.del(UpLoadConstant.task + taskId);
                redisUtil.del(UpLoadConstant.chunkNum + taskId);
                redisUtil.del(UpLoadConstant.fastDfsPath + taskId);
            }
        } catch (IOException e) {
            e.printStackTrace();
            String md5 = (String) redisUtil.getObject(UpLoadConstant.task + taskId);
            redisUtil.del(UpLoadConstant.fileMd5 + md5);
            redisUtil.del(UpLoadConstant.task + taskId);
            redisUtil.del(UpLoadConstant.chunkNum + taskId);
            redisUtil.del(UpLoadConstant.fastDfsPath + taskId);
            map.put("result", "上传异常");
        } finally {
            try {
                if (raf != null) {
                    raf.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return ResultUtil.success(map);
    }

    /**
     * 校验md5值
     *
     * @param md5 md5
     * @return map
     */
    public Map<String, Object> checkMd5(String md5) {
        Map<String, Object> map = new HashMap<>();
        String fileSize = null;
        String taskId = null;
        md5 = SecureUtil.md5(md5);
        Map redisMap = redisUtil.getMap(UpLoadConstant.fileMd5 + md5);
        if (MapUtil.isNotEmpty(redisMap)) {
            fileSize = ((String) redisMap.get("fileSize"));
            taskId = ((String) redisMap.get("taskId"));
        }
        if (StrUtil.isNotEmpty(fileSize)) {
            map.put("fileSize", Long.parseLong(fileSize != null ? fileSize : ""));
        } else {
            Map<String, Object> map1 = new HashMap<>();
            taskId = IdUtil.simpleUUID();
            map1.put("fileSize", 0);
            map1.put("taskId", taskId);
            redisUtil.setHashAsMap(UpLoadConstant.fileMd5 + md5, map1, cacheTime);
            redisUtil.setObject(UpLoadConstant.task + taskId, md5, cacheTime);
            map.put("fileSize", 0);
        }
        map.put("taskId", taskId);
        return map;
    }
}
